#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
 
int main(int argc,char *argv[]) {
  if (argc!=2) {
    printf("Using:./server port\nExample:./server 5005\n\n"); return -1;
  }
 
  // 第1步：创建服务端的socket。
  int listenfd;
  /**
   * // creates an endpoint for communication and returns a descriptor
   * int socket(int af, int type, int protocol);
   *  af 为地址族（Address Family），也就是 IP 地址类型，常用的有 AF_INET 和 AF_INET6
   *    AF_INET 表示 IPv4 地址；AF_INET6 表示 IPv6 地址
   *      你也可以使用PF前缀，PF是“Protocol Family”的简写，
   *      它和AF是一样的。例如，PF_INET 等价于 AF_INET，PF_INET6 等价于 AF_INET6。
   *  type 为数据传输方式，常用的有 SOCK_STREAM 和 SOCK_DGRAM
   *    SOCK_STREAM：tcp 协议
   *    SOCK_DGRAM：udp 协议
   *  protocol 表示传输协议，常用的有 IPPROTO_TCP 和 IPPTOTO_UDP 和 0
   *    IPPROTO_TCP：TCP 传输协议
   *    IPPROTO_UDP：UDP 传输协议。
   *    0：操作系统通过 af 和 type 自动推演出应该使用什么协议
   */
  if ( (listenfd = socket(AF_INET,SOCK_STREAM,0)) == -1) { 
    perror("socket"); return -1;
  }
 
  // 第2步：把服务端用于通信的地址和端口绑定到socket上。
  /**
	 * 	struct sockaddr_in {
   *  	sa_family_t     sin_family;   //地址族（Address Family），也就是地址类型
   *  	uint16_t        sin_port;     //16位的端口号
   *  	struct in_addr  sin_addr;     //32位IP地址
   *  	char            sin_zero[8];  //不使用，一般用0填充
	 *  };
   *  sin_family 和 socket() 的第一个参数的含义相同，地址族。
   *  sin_prot 为端口号。
   *    uint16_t 的长度为两个字节，
   *    理论上端口号的取值范围为 0~65536，但 0~1023 的端口一般由系统分配给特定的服务程序，
   *    例如 Web 服务的端口号为 80，FTP 服务的端口号为 21，
   *    所以我们的程序要尽量在 1024~65536 之间分配端口号。
   *  sin_addr 是 struct in_addr 结构体类型的变量
   *    struct in_addr{
   *      in_addr_t  s_addr;  //32位的IP地址
   *    };
   *    in_addr_t 在头文件 <netinet/in.h> 中定义，等价于 unsigned long，长度为4个字节
   *  sin_zero[8] 是多余的8个字节，没有用，一般使用 memset() 函数填充为 0。
   */
  struct sockaddr_in servaddr;    // 服务端地址信息的数据结构。
  memset(&servaddr,0,sizeof(servaddr));
  servaddr.sin_family = AF_INET;  // 协议族，在socket编程中只能是AF_INET。
  servaddr.sin_addr.s_addr = htonl(INADDR_ANY);          // 任意ip地址。
  //servaddr.sin_addr.s_addr = inet_addr("192.168.190.134"); // 指定ip地址。
  /**
   * htons()：host to network short，将short类型数据从主机字节序转换为网络字节序。
   * ntohs()：network to host short，将short类型数据从网络字节序转换为主机字节序。
   * htonl()：host to network long，将long类型数据从主机字节序转换为网络字节序。
   * ntohl()：network to host long，将long类型数据从网络字节序转换为主机字节序。
   *
   * 以s为后缀的函数中，s代表2个字节short，因此用于端口号转换；
   * 以l为后缀的函数中，l代表4个字节的long，因此用于IP地址转换。
   */
  servaddr.sin_port = htons(atoi(argv[1]));  // 指定通信端口
  /**
   * // assign a local socket address address to a socket identified by descriptor socket that has no  local  socket  address  assigned
   * int bind(int sock, struct sockaddr *addr, socklen_t addrlen);
   *   sock 为 socket 文件描述符，
   *   addr 为 sockaddr 结构体变量的指针，
   *      struct sockaddr {
   *        sa_family_t  sin_family;   //地址族（Address Family），也就是地址类型
   *        char         sa_data[14];  //IP地址和端口号
   *      };
   *      // bind() 第二个参数的类型为 sockaddr，而代码中却使用 sockaddr_in，然后再强制转换为 sockaddr，这是为什么呢？
   *      sockaddr 和 sockaddr_in 的长度相同，都是16字节，只是将IP地址和端口号合并到一起，用一个成员 sa_data 表示。
   *      要想给 sa_data 赋值，必须同时指明IP地址和端口号，例如”127.0.0.1:80“，遗憾的是，没有相关函数将这个字符串转换成需要的形式，也就很难给 sockaddr 类型的变量赋值，所以使用 sockaddr_in 来代替。
   *      这两个结构体的长度相同，强制转换类型时不会丢失字节，也没有多余的字节。
   *      
   *      可以认为，sockaddr 是一种通用的结构体，可以用来保存多种类型的IP地址和端口号，
   *      而 sockaddr_in 是专门用来保存 IPv4 地址的结构体。
   *      另外还有 sockaddr_in6，用来保存 IPv6 地址，它的定义如下：
   *      struct sockaddr_in6 { 
   *        sa_family_t sin6_family;  //(2)地址类型，取值为AF_INET6
   *        in_port_t sin6_port;  //(2)16位端口号
   *        uint32_t sin6_flowinfo;  //(4)IPv6流信息
   *        struct in6_addr sin6_addr;  //(4)具体的IPv6地址
   *        uint32_t sin6_scope_id;  //(4)接口范围ID
   *      };
   *   addrlen 为 addr 变量的大小，可由 sizeof() 计算得出。
   */
  if (bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) != 0 )
  { perror("bind"); close(listenfd); return -1; }
 
  // 第3步：把socket设置为监听模式。
  /**
   * 通过 listen() 函数可以让套接字进入被动监听状态
   * 所谓被动监听，是指当没有客户端请求时，套接字处于“睡眠”状态，只有当接收到客户端请求时，套接字才会被“唤醒”来响应请求。
   * int listen(int sock, int backlog);
   *  sock 为需要进入监听状态的套接字
   *  backlog 为请求队列的最大长度
   *
   * 请求队列:
   *   当套接字正在处理客户端请求时，如果有新的请求进来，套接字是没法处理的，只能把它放进缓冲区，待当前请求处理完毕后，再从缓冲区中读取出来处理。
   *   如果不断有新的请求进来，它们就按照先后顺序在缓冲区中排队，直到缓冲区满。这个缓冲区，就称为请求队列（Request Queue）。
   *   缓冲区的长度（能存放多少个客户端请求）可以通过 listen() 函数的 backlog 参数指定，但究竟为多少并没有什么标准，可以根据你的需求来定，并发量小的话可以是10或者20。
   *   如果将 backlog 的值设置为 SOMAXCONN，就由系统来决定请求队列长度，这个值一般比较大，可能是几百，或者更多。
   *   当请求队列满时，就不再接收新的请求，对于 Linux，客户端会收到 ECONNREFUSED 错误，对于 Windows，客户端会收到 WSAECONNREFUSED 错误。
   */
  if (listen(listenfd,5) != 0 ) { perror("listen"); close(listenfd); return -1; }
 
  // 第4步：接受客户端的连接。
  int  clientfd;                  // 客户端的socket。
  int  socklen=sizeof(struct sockaddr_in); // struct sockaddr_in的大小
  struct sockaddr_in clientaddr;  // 客户端的地址信息。
  /**
   * 当套接字处于监听状态时，可以通过 accept() 函数来接收客户端请求。
   * accept() 返回一个新的套接字来和客户端通信
   * addr 保存了客户端的IP地址和端口号，而 sock 是服务器端的套接字
   * int accept(int sock, struct sockaddr *addr, socklen_t *addrlen); 
   *  sock 为服务器端套接字
   *  addr 为 sockaddr_in 结构体变量
   *  addrlen 为参数 addr 的长度，可由 sizeof() 求得。
   */
  clientfd = accept(listenfd,(struct sockaddr *)&clientaddr,(socklen_t*)&socklen);
  printf("客户端（%s）已连接。\n",inet_ntoa(clientaddr.sin_addr));
 
  // 第5步：与客户端通信，接收客户端发过来的报文后，回复ok。
  char buffer[1024];
  while (1) {
    int iret;
    memset(buffer, 0, sizeof(buffer));
    /**
     * 可使用 recv 和 read 接受客户端的请求报文
     * linux 下一切皆文件，推荐使用 read
     * // int recv(SOCKET sock, char *buf, int len, int flags);
     * if ( (iret=recv(clientfd, buffer, sizeof(buffer),0)) <= 0) { //接收客户端的请求报文
     *   printf("iret=%d\n",iret); break;  
     * }
     */
    /**
     * ssize_t read(int fd, void *buf, size_t nbytes);
     *   fd 为要读取的文件的描述符
     *   buf 为要接收数据的缓冲区地址
     *   nbytes 为要读取的数据的字节数。
     */
    if ( (iret=read(clientfd, buffer, sizeof(buffer))) <= 0) { // 接收客户端的请求报文
       printf("iret=%d\n",iret); break;  
    }
    printf("接收：%s\n",buffer);
 
    strcpy(buffer,"ok");
    /**
     * 可使用 send 和 write 向客户端发送响应结果，推荐使用 write
     * // int send(SOCKET sock, const char *buf, int len, int flags);
     * if ( (iret=send(clientfd, buffer, strlen(buffer),0)) <= 0) // 向客户端发送响应结果。
     * { perror("send"); break; }
     */
    /**
     * ssize_t write(int fd, const void *buf, size_t nbytes);
     *   fd 为要写入的文件的描述符
     *   buf 为要写入的数据的缓冲区地址
     *   nbytes 为要写入的数据的字节数。
     */
    if ( (iret=write(clientfd, buffer, strlen(buffer))) <= 0) { // 向客户端发送响应结果。
      perror("send"); break;
    }
    printf("发送：%s\n",buffer);
  }
 
  // 第6步：关闭socket，释放资源。
  close(listenfd); close(clientfd);
}
